package cn.jtool.dig.common.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.Lifecycle;

import java.net.InetSocketAddress;
import java.util.Date;
import java.util.concurrent.TimeUnit;

public class TcpClient implements Lifecycle {
    private static final Logger LOG = LoggerFactory.getLogger(TcpClient.class);
    private final Bootstrap clientBootstrap;
    private final EventLoopGroup group;
    private volatile boolean started;
    private ChannelFuture future = null;

    @Getter
    private Date connectTime;

    private final String host;

    private final int port;

    @Getter
    private long channelId;

    private int retryCount;

    public TcpClient(long channelId, String host, int port, ChannelHandler channelHandler) {
        this.channelId = channelId;
        this.host = host;
        this.port = port;
        clientBootstrap = new Bootstrap();
        this.group = new NioEventLoopGroup();
        clientBootstrap.channel(NioSocketChannel.class);
        clientBootstrap.group(group);
        clientBootstrap.handler(channelHandler);
    }


    public TcpClient(long channelId, String host, int port, int retryCount, ChannelHandler channelHandler) {
        this.channelId = channelId;
        this.host = host;
        this.port = port;
        this.retryCount = retryCount;
        clientBootstrap = new Bootstrap();
        this.group = new NioEventLoopGroup();
        clientBootstrap.channel(NioSocketChannel.class);
        clientBootstrap.group(group);
        clientBootstrap.handler(channelHandler);
    }

    private int connectTimes = 0;

    @Override
    public void start() {
        try {
            future = clientBootstrap.connect(new InetSocketAddress(
                    host, port)).sync();
            this.connectTime = new Date();
            started = this.future.channel().isOpen();
            this.connectTimes = 0;
        } catch (Exception e) {
            LOG.error("Unable to start TCP client, {}:{}",this.host, this.port, e);
            if(this.connectTimes < this.retryCount){
                this.connectTimes ++;
                this.group.schedule(this::start, 1000, TimeUnit.MILLISECONDS);
            }
        }
    }

    public void reConnect(){
        this.reConnect(1);
    }

    public void reConnect(int maxRetryCount){
        this.start();
    }

    public void restart(){
        if(!this.started){
            this.stop();
        }
        this.start();
    }

    @Override
    public void stop() {
        if (!started) {
            return;
        }
        try {
            this.group.shutdownGracefully();
            started = false;
        } catch (Exception e) {
            LOG.warn("Unable to stop client, {}:{}", this.host, this.port, e);
        }
    }

    @Override
    public boolean isRunning() {
        return started;
    }

    public Channel getChannel() throws InterruptedException {
        return future.sync().channel();
    }

}
